package net.sf.midollo.net.protocol.multitunnel;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.EOFException;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.io.OutputStream;

import net.sf.midollo.io.IOStream;
import net.sf.midollo.io.IOStreamConnector;
import net.sf.midollo.io.IOUtils;
import net.sf.midollo.net.protocol.multitunnel.server.MultiTunnelServer;
import net.sf.midollo.utils.Map;
import net.sf.midollo.utils.Utils;

public abstract class MultiTunnelManager extends IOStreamConnector {

  protected final Object lock = new Object();

  protected Map sessions;
  protected IOStream ioTunnel;
  protected DataInputStream disTunnel;
  protected DataOutputStream dosTunnel;
  protected Thread threadTunnel;

  public MultiTunnelManager() {
    super(null, null, 0);
  }

  public void runConnector() throws IOException {
    while (readFromTunnelWriteToSession()) {
    }
  }

  protected void connectTunnelManager(IOStream ioTunnel, boolean sync) throws IOException {
    if (this.ioTunnel == null) {
      this.sessions = Utils.newMap();
      this.ioTunnel = ioTunnel;
      this.disTunnel = IOUtils.toDataInputStream(ioTunnel.getInput());
      this.dosTunnel = IOUtils.toDataOutputStream(ioTunnel.getOutput());
      if (sync) {
        runConnector();
      } else {
        this.threadTunnel = new Thread(this);
        this.threadTunnel.start();
      }
    }
  }

  protected void disconnectTunnelManager() throws IOException {
    try {
      if (this.ioTunnel != null)
        this.ioTunnel.close();
    } finally {
      this.ioTunnel = null;
      try {
        if (this.threadTunnel != null)
          this.threadTunnel.join();
      } catch (InterruptedException ex) {
        if (this.error == null)
          this.error = new InterruptedIOException(ex.toString());
      } finally {
        this.threadTunnel = null;
      }
    }
  }

  protected boolean readFromTunnelWriteToSession() throws IOException {
    try {
      // Utils.log("readFromTunnelWriteToSession");
      int sessionId = this.disTunnel.readInt();
      // Utils.log("MTM" + sessionId);
      if (sessionId > 0) {

        int length = this.disTunnel.readInt();
        checkValidLength(length);

        if (this.buffer == null || length > this.buffer.length)
          this.buffer = new byte[length];
        // Utils.log("MTM" + sessionId + " R" + length);
        this.disTunnel.readFully(this.buffer, 0, length);

        MultiTunnelSession session;
        synchronized (this.lock) {
          session = (MultiTunnelSession)this.sessions.get(new Integer(sessionId));
          if (session == null) {
            session = createSession(sessionId);
            if (session != null)
              addSession(session);
          }
        }
        if (session != null) {
          OutputStream sessionOut = session.getOutput();
          // Utils.log("MTM" + sessionId + " W" + length);
          sessionOut.write(this.buffer, 0, length);
          sessionOut.flush();
        }

      } else {

        sessionId = -sessionId;
        MultiTunnelSession session;
        synchronized (this.lock) {
          session = (MultiTunnelServer)this.sessions.get(new Integer(sessionId));
          if (session != null)
            removeSession(sessionId, false);
        }
        if (session != null)
          session.destroySession();
      }
      return true;
    } catch (EOFException ex) {
      return false;
    }
  }

  protected abstract MultiTunnelSession createSession(int sessionId) throws IOException;

  protected void writeToTunnel(int sessionId, byte[] buffer, int off, int len) throws IOException {
    this.dosTunnel.writeInt(sessionId);
    this.dosTunnel.writeInt(len);
    this.dosTunnel.write(buffer, off, len);
    this.dosTunnel.flush();
  }

  protected void addSession(MultiTunnelSession session) throws IOException {
    this.sessions.put(new Integer(session.getSessionId()), session);
  }

  protected void removeSession(int sessionId, boolean notifyTunnel) throws IOException {
    MultiTunnelSession session = (MultiTunnelSession)this.sessions.remove(new Integer(sessionId));
    if (notifyTunnel && session != null) {
      this.dosTunnel.writeInt(-sessionId);
      this.dosTunnel.flush();
    }
  }

  protected void checkValidSessionId(int sessionId) throws IOException {
    if (sessionId <= 0)
      throw new IOException("Invalid sessionId: " + sessionId);
  }

  protected void checkValidLength(int length) throws IOException {
    if (length <= 0)
      throw new IOException("Invalid length: " + length);
  }
}
